﻿/*
 *  This file is part of SXAPI.
 *
 *  SXAPI is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation, version 3 of the License.

 *  SXAPI is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *  
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with SXAPI.  If not, see <http://www.gnu.org/licenses/>.
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace StackExchangeApi
{
    /// <summary>
    /// Base class for various, very agressive, caches.
    /// </summary>
    /// <typeparam name="T">Type held in the cache.  T must have a (long, StackExchangeSite) constructor</typeparam>
    internal abstract class AllocationCache<T>
    {
        /// <summary>
        /// Alright, this is a little odd, but trust me.
        /// 
        /// Every unique T class is going to get its own copy of CACHE.
        /// This is a consequence of the implementation of generics in C#.
        /// </summary>
        private static Dictionary<Pair<StackExchangeSite, long>, T> CACHE = new Dictionary<Pair<StackExchangeSite, long>, T>();

        /// <summary>
        /// Get an item with the given id &amp; site from the cache,
        /// or allocate it.
        /// </summary>
        /// <param name="id">Id of the item</param>
        /// <param name="site">Site the item is on</param>
        /// <returns>An item of T with the Id = id and Site = site</returns>
        internal static T GetOrAllocate(long id, StackExchangeSite site)
        {
            lock (CACHE)
            {
                Pair<StackExchangeSite, long> key = new Pair<StackExchangeSite, long>(site, id);
                T ret;

                if (CACHE.TryGetValue(key, out ret))
                    return ret;

                ret = Allocate(id, site);

                CACHE[key] = ret;

                return ret;
            }
        }

        /// <summary>
        /// Clear the cache.
        /// 
        /// Not intended for general use.
        /// </summary>
        internal static void Clear()
        {
            lock (CACHE)
            {
                CACHE.Clear();
            }
        }

        /// <summary>
        /// Allocate an object of the type T.
        /// 
        /// T must have a constructor (long, StackExchangeSite).
        /// </summary>
        /// <param name="id">Id of the item</param>
        /// <param name="site">Site of the item</param>
        /// <returns>Equivalent to "new T(id, site)"</returns>
        private static T Allocate(long id, StackExchangeSite site)
        {
            ConstructorInfo con = typeof(T).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, Type.DefaultBinder, new Type[] { typeof(long), typeof(StackExchangeSite) }, null);

            return (T)con.Invoke(new Object[] { id, site });
        }
    }
}
